/**
 *       Copyright (c) 2012 Lukas Zaruba
 *
 *   This file is part of Robot Playground.
 *
 *   Robot Playground is free software: you can redistribute it and/or modify
 *   it under the terms of the GNU Lesser General Public License as published by
 *   the Free Software Foundation, either version 3 of the License, or
 *   (at your option) any later version.
 *
 *   Robot Playground is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU Lesser General Public License for more details.
 *
 *   You should have received a copy of the GNU Lesser General Public License
 *   along with Robot Playground. If not, see <http://www.gnu.org/licenses/>.
 */
package net.zarubsys.robotplayground.core.session;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;

import net.zarubsys.robotplayground.core.device.IDevice;
import net.zarubsys.robotplayground.core.device.IDeviceConfiguration;
import net.zarubsys.robotplayground.core.modules.IModule;
import net.zarubsys.robotplayground.core.modules.IModuleConfiguration;
import net.zarubsys.robotplayground.core.monitors.IMonitor;
import net.zarubsys.robotplayground.core.program.IProgram;
import net.zarubsys.robotplayground.core.program.IProgramConfiguration;

import org.apache.log4j.Logger;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.InvalidRegistryObjectException;
import org.eclipse.core.runtime.Platform;

/**
 * SessionsHelper
 *
 * @author Lukas Zaruba, lukas.zaruba@gmail.com
 */
public class SessionsHelper {
	
	private static final String DEVICES_EXT_POINT_ID = "net.zarubsys.robotplayground.core.devices";
	private static final String MODULES_EXT_POINT_ID = "net.zarubsys.robotplayground.core.modules";
	private static final String MONITORS_EXT_POINT_ID = "net.zarubsys.robotplayground.core.monitors";
	private static final String PROGRAMS_EXT_POINT_ID = "net.zarubsys.robotplayground.core.programs";
	
	private final Map<String, Class<IDevice<IDeviceConfiguration>>> devicesCache = new HashMap<>();
	private final Map<String, Class<IModule<IDevice<?>, IModuleConfiguration>>> modulesCache = new HashMap<>();
	private final Map<String, Class<IMonitor>> monitorsCache = new HashMap<>();
	private final Map<String, Class<IProgram<IProgramConfiguration>>> programsCache = new HashMap<>();
	
	private final Map<Session, RunningSession> runningSessions = new HashMap<>();
	
	private static final Logger LOG = Logger.getLogger(SessionsHelper.class);
	
	private static final SessionsHelper INSTANCE = new SessionsHelper();
	
	private SessionsHelper() {
		// to prevent instantiation
		init();
	}
	
	private <T> void loadObjectClass(String extPointId, Map<String, Class<T>> cache) {
		IConfigurationElement[] cfgs = Platform.getExtensionRegistry().getConfigurationElementsFor(extPointId);
		for (IConfigurationElement cfg : cfgs) {
			String id = cfg.getAttribute("id");
			String klassName = cfg.getAttribute("class");
			try {
				@SuppressWarnings("unchecked") // checked in extension point definition
				Class<T> klass = (Class<T>) Class.forName(klassName);
				if (devicesCache.containsKey(id)) {
					LOG.warn("There already is object for id '" + id + " registered in extension point " + cfg.getName() + " Skipping the one from bundle " + cfg.getContributor().getName() + "...");
					continue;
				}
				cache.put(id, klass);
			} catch (ClassNotFoundException | InvalidRegistryObjectException e) {
				LOG.error("Error while loading class '" + klassName + "! Device for id '" + id + "' from bundle " + cfg.getContributor().getName() + " will be skipped...", e);
			}
		}
	}

	private void init() {
		loadObjectClass(DEVICES_EXT_POINT_ID, devicesCache);
		loadObjectClass(MODULES_EXT_POINT_ID, modulesCache);
		loadObjectClass(MONITORS_EXT_POINT_ID, monitorsCache);
		loadObjectClass(PROGRAMS_EXT_POINT_ID, programsCache);
	}
	
	public static SessionsHelper getInstance() {
		return INSTANCE;
	}

	public Session loadSession(File file) {
		Session result = null;
		try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file))) {
			result = (Session) ois.readObject();
		} catch (ClassNotFoundException | IOException e) {
			LOG.error("Error reading measuring settings.", e);
			throw new RuntimeException(e);
		}
		
		return result;
	}
	
	public void saveSession(Session session, File file) {
		try (ObjectOutputStream os = new ObjectOutputStream(new FileOutputStream(file))) {
			os.writeObject(session);
		} catch (IOException e) {
			LOG.error("Error while saving session.", e);
			throw new RuntimeException(e);
		}
	}
	
	public <T> T getSystemObject(Map<String, Class<T>> sourceMap, String id, String type) {
		Class<T> klass = sourceMap.get(id);
		if (klass == null) {
			throw new IllegalArgumentException("There is no " + type + " registered for id " + id + "!");
		}
		return instantiate(klass);
	}

	public IDevice<IDeviceConfiguration> getDevice(String id) {
		return getSystemObject(devicesCache, id, "device");
	}
	
	public IModule<IDevice<?>, IModuleConfiguration> getModule(String id) {
		return getSystemObject(modulesCache, id, "module");
	}
	
	public IMonitor getMonitor(String id) {
		return getSystemObject(monitorsCache, id, "monitor");
	}
	
	public IProgram<IProgramConfiguration> getProgram(String id) {
		return getSystemObject(programsCache, id, "program");
	}
	
	private <T> T instantiate(Class<T> klass) {
		try {
			return klass.getConstructor().newInstance();
		} catch (InstantiationException | IllegalAccessException
				| IllegalArgumentException | InvocationTargetException
				| NoSuchMethodException | SecurityException e) {
			LOG.error("Error while instantiating klass " + klass.getName() + "!", e);
			throw new RuntimeException(e);
		}
	}
	
	public void registerRunningSession(Session session, RunningSession runningSession) {
		if (runningSessions.containsKey(session)) {
			throw new IllegalStateException("Session can run only once at a time!");
		}
		runningSessions.put(session, runningSession);
	}
	
	public RunningSession getRunningSession(Session session) {
		return runningSessions.get(session);
	}
	
	public void removeRunningSession(Session session) {
		RunningSession removed = runningSessions.remove(session);
		if (removed == null) {
			throw new IllegalArgumentException("There was no running session for the given session!");
		}
	}
	
}

